package cn.tedu.charging.device.service.impl;

import cn.tedu.charging.common.pojo.vo.GunInfoVO;
import cn.tedu.charging.common.pojo.vo.StationDetailVO;
import cn.tedu.charging.common.pojo.vo.StationInfoVO;
import cn.tedu.charging.device.dao.mapper.GunMapper;
import cn.tedu.charging.device.dao.mapper.StationMapper;
import cn.tedu.charging.device.pojo.po.GunPO;
import cn.tedu.charging.device.pojo.po.StationPO;
import cn.tedu.charging.device.pojo.vo.StationVO;
import cn.tedu.charging.device.service.DeviceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import cn.tedu.charging.common.pojo.param.UpdateGunStatusParam;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
public class DeviceServiceImpl implements DeviceService {
    @Override
    public List<StationInfoVO> nearStation(Double longitude, Double latitude, Double distance) {
        //redisTemplate.opsForGeo()   操作GEO类型的Redis命令
        GeoOperations geoOperations = redisTemplate.opsForGeo();
        // opsForValue() 操作字符串类型的数据
        //泛型  key String    ,value  StationPO
        ValueOperations<String, StationPO> valueOperations = redisTemplate.opsForValue();
        //通过用户的入参 经纬度  创建一个用户所在位置的点
        Point point = new Point(longitude, latitude);
        //通过用户的入参 距离  创建一个距离对象    Metrics 导包选择  import org.springframework.data.geo.Metrics;
        // Metrics.KILOMETERS  单位：公里 千米
        //redius 半径\  范围
        Distance redius = new Distance(distance);
        //通过  用户的位置 和 要查找的范围  创建一个圆形区域
        Circle circle = new Circle(point, redius);
        //封装查询选项WITHDIST
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeDistance();
        //固定写法 通过Redis的GEO类型的命令 查找  在指定区域内的所有场站数据 放到 stations中
        GeoResults<RedisGeoCommands.GeoLocation<Integer>> stations = geoOperations.radius(STATION_GEO_KEY, circle,args);
        //查到的数据要  转换为  我们要的结果  创建一个集合  用来存储结果
        List<GeoResult<RedisGeoCommands.GeoLocation<Integer>>> content = stations.getContent();
        //定义返回结果  vo  list
        List<StationInfoVO> list = new ArrayList<>();
        for (GeoResult<RedisGeoCommands.GeoLocation<Integer>> geoLocationGeoResult : content) {
            //name 表示的是 存储的时候那个member  存一起是场站信息  分开存是场站id
            Integer stationId = geoLocationGeoResult.getContent().getName();
            //distance 表示获取当前元素值stationId 与中心点的距离
            Double distanceValue = geoLocationGeoResult.getDistance().getValue();
            String key = STATION_DETAIL_KEY + stationId;
            //通过key  查询value
            StationPO stationPO = valueOperations.get(key);
            //创建一个vo对象
            StationInfoVO stationVO = new StationInfoVO();
            /**
             * stationVO 现在的类型 是 stationInfoVO
             * stationId: redis 中的geo元素值
             * stationLat: 从数据库获取po
             * stationLng: 从数据库获取po
             * stationName: 从数据库获取po
             * stationStatus: 从数据库获取po po没有 0营业中 其他未营业
             * distance: 从redis获取 georadius key value withdist 手动封装一个假的
             * avgPrice: 数据库和redis都没有 应该从订单管理中心,做历史数据分析得到 手动封装一个假的
             */
            //把stationPO数据 赋值给stationVO  拷贝
            BeanUtils.copyProperties(stationPO, stationVO);
            //lat和lng在po中是double但是vo中要的是BigDecimal
            stationVO.setStationLat(new BigDecimal(stationPO.getStationLat()));
            stationVO.setStationLng(new BigDecimal(stationPO.getStationLng()));
            stationVO.setStationId(stationId);
            stationVO.setStationStatus(0);
            stationVO.setDistance(distanceValue);
            stationVO.setAvgPrice(1.27d);
            //把stationVO数据 添加到list中
            list.add(stationVO);
        }
        return list;
    }

    //存储场站位置信息的Redis key
    private String STATION_GEO_KEY = "station_geo_key";
    //存储场站详情信息的Redis key
    private String STATION_DETAIL_KEY = "station_detail_";

    @Autowired
    StationMapper stationMapper;
    @Autowired
    GunMapper gunMapper;
    @Override
    public StationDetailVO stationDetailById(Integer stationId) {
        //从redis缓存获取的
        StationPO stationByPOById = getStationPOById(stationId);
        if (stationByPOById != null) {
            StationDetailVO stationVO = new StationDetailVO();
            //封装stationId
            stationVO.setStationId(stationByPOById.getId());
            //封装 stationName lat lng在这个接口没有
            stationVO.setStationName(stationByPOById.getStationName());
            stationVO.setAddress(stationByPOById.getAddress());
            //使用stationId 查询gun列表信息
            List<GunPO> guns=gunMapper.selectGunsByStationId(stationId);
            //判断是否为空,非空的时候交给vo属性
            if (guns!=null&&guns.size()>0){
                List<GunInfoVO> gunInfoVOS = new ArrayList<>();
                for (GunPO gun : guns) {
                    GunInfoVO gunInfoVO = new GunInfoVO();
                    BeanUtils.copyProperties(gun, gunInfoVO);
                    gunInfoVOS.add(gunInfoVO);
                }
                //给station复制
                stationVO.setGunInfoVos(gunInfoVOS);
            }
            return stationVO;
        }
        return null;
    }

    @Override
    public void initStationData() {
        //1.从数据库查询所有的充电站
        List<StationPO> allStations = getAllStations();
        //保存场站信息到Redis的GEO中
        saveStationPO2RedisGeo(allStations);

    }

    /**
     * 更新枪的状态
     *
     * @param updateGunStatusParam 枪的状态参数
     * @return 更新结果
     */
    @Override
    public Boolean updateGunStatus(UpdateGunStatusParam updateGunStatusParam) {
        return true;
    }

    //     从数据库查询查询所有的充电站
    private List<StationPO> getAllStations() {
        return stationMapper.selectList(null);
    }

    //保存场站信息到Redis的GEO中
    private void saveStationPO2RedisGeo(List<StationPO> allStations) {
        //opsForGeo() 操作GEO类型的Redis命令
        GeoOperations geoOperations = redisTemplate.opsForGeo();
        // opsForValue() 操作字符串类型的数据
        //泛型  key String    ,value  StationPO
        ValueOperations<String, StationPO> valueOperations = redisTemplate.opsForValue();

        for (StationPO stationPO : allStations) {
            // 获取场站的经纬度  赋值给Geo点 进而保存到Redis的GEO中
            Double stationLng = stationPO.getStationLng();
            Double stationLat = stationPO.getStationLat();
            //创建Point对象 点 充电站的经纬度  Point包不要导错，要用geo
            Point point = new Point(stationLng, stationLat);
            //保存到Redis的GEO中
            //Long add(K key, Point point, M member); //GEOADD 命令
            //key  stations
            //point  充电站的经纬度 2
            geoOperations.add(STATION_GEO_KEY, point, stationPO.getId());

            String key = STATION_DETAIL_KEY + stationPO.getId();
            //保存到Redis的字符串中
            valueOperations.set(key, stationPO);


        }

    }


    private StationPO getStationPOById(Integer stationId) {
        //1.根据id查询充电站的详情 redis缓存查询
        StationPO stationByRedisPO = getStationByRedis(stationId);
        //2.如果缓存中存在,则直接返回缓存中的数据
        if (stationByRedisPO != null) {
            //如果有数据，直接返回Redis中的数据
            return stationByRedisPO;
        } else {
            StationPO stationByDBPO = getStationByDB(stationId);
            if (stationByDBPO != null) {
                //3.如果缓存中不存在,则查询数据库,并将查询结果存入Redis缓存
                saveStationPO2Redis(stationByDBPO);
                //如果有数据，直接返回DB中的数据
                return stationByDBPO;
            }
        }
        return null;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    //根据id查询充电站的详情 redis缓存查询
    private StationPO getStationByRedis(Integer stationId) {
        //                                        opsForValue() 操作字符串类型的数据
        //泛型  key String    ,value  StationPO
        //                                                ops operations 运营；运作；业务操作
        ValueOperations<String, StationPO> valueOperations = redisTemplate.opsForValue();
        String key = STATION_DETAIL_KEY + stationId;
        StationPO stationPO = valueOperations.get(key);
        return stationPO;
    }

    //根据id查询充电站的详情 db数据库查询
    private StationPO getStationByDB(Integer stationId) {
        StationPO stationPO = stationMapper.selectById(stationId);
        return stationPO;
    }

    /**
     * 将充电站详情保存到redis中
     *
     * @param stationPO 充电站详情
     */
    private void saveStationPO2Redis(StationPO stationPO) {
        //                                        opsForValue() 操作字符串类型的数据
        //泛型  key String    ,value  StationPO
        ValueOperations<String, StationPO> valueOperations = redisTemplate.opsForValue();
        String key = STATION_DETAIL_KEY + stationPO.getId();
        valueOperations.set(key, stationPO);
    }
}
